import os
import threading
import time
import tkinter as tk
from datetime import datetime
from tkinter import ttk, filedialog, messagebox, scrolledtext

import intelhex
import ttkbootstrap as ttk
from ttkbootstrap.constants import *

from app.util.annotation_util import annotate, ClassInfo
from app.view.base_view import View


@annotate(ClassInfo(name="Hex&Bin", category="tool"))
class HexBinView(View):
    """设置视图"""

    def __init__(self, parent, controller):
        super().__init__(parent, controller)
        # 初始化变量
        self.input_files = []
        self.output_file = ""
        self.base_address = tk.StringVar(value="0x00000000")
        self.processed_data = None  # 存储处理后的二进制数据

        # 创建UI
        self._create_widgets()

    def _create_widgets(self):
        """创建界面组件"""
        # 主容器
        main_container = ttk.Frame(self, padding=10)
        main_container.pack(fill=BOTH, expand=YES)

        # 功能选择区域
        function_frame = ttk.LabelFrame(main_container, text="功能选择", padding=10)
        function_frame.pack(fill=X, pady=5)

        self.function_var = tk.StringVar(value="hex2bin")

        ttk.Radiobutton(
            function_frame,
            text="HEX转BIN",
            variable=self.function_var,
            value="hex2bin",
            command=self.update_ui_based_on_function
        ).pack(side=LEFT, padx=10)

        ttk.Radiobutton(
            function_frame,
            text="BIN转HEX",
            variable=self.function_var,
            value="bin2hex",
            command=self.update_ui_based_on_function
        ).pack(side=LEFT, padx=10)

        ttk.Radiobutton(
            function_frame,
            text="合并HEX文件",
            variable=self.function_var,
            value="merge_hex",
            command=self.update_ui_based_on_function
        ).pack(side=LEFT, padx=10)

        ttk.Radiobutton(
            function_frame,
            text="合并BIN文件",
            variable=self.function_var,
            value="merge_bin",
            command=self.update_ui_based_on_function
        ).pack(side=LEFT, padx=10)

        # 参数设置区域
        param_frame = ttk.LabelFrame(main_container, text="参数设置", padding=10)
        param_frame.pack(fill=X, pady=5)

        # 基地址设置（主要用于BIN转HEX和合并BIN）
        self.base_addr_frame = ttk.Frame(param_frame)

        ttk.Label(self.base_addr_frame, text="基地址:").pack(side=LEFT, padx=5)
        ttk.Entry(
            self.base_addr_frame,
            textvariable=self.base_address,
            width=15
        ).pack(side=LEFT, padx=5)
        ttk.Label(self.base_addr_frame, text="(十六进制，如0x08000000)").pack(side=LEFT, padx=5)

        # 文件选择区域
        file_frame = ttk.LabelFrame(main_container, text="文件选择", padding=10)
        file_frame.pack(fill=X, pady=5)

        # 输入文件
        input_frame = ttk.Frame(file_frame)
        input_frame.pack(fill=X, pady=5)

        ttk.Label(input_frame, text="输入文件:").pack(side=LEFT, padx=5)

        self.input_files_var = tk.StringVar(value="未选择文件")
        ttk.Label(input_frame, textvariable=self.input_files_var).pack(side=LEFT, padx=5, fill=X, expand=YES)

        ttk.Button(
            input_frame,
            text="添加文件",
            command=self.add_input_files,
            bootstyle=INFO
        ).pack(side=LEFT, padx=5)

        ttk.Button(
            input_frame,
            text="清除",
            command=self.clear_input_files,
            bootstyle=SECONDARY
        ).pack(side=LEFT, padx=5)

        # 输出文件
        output_frame = ttk.Frame(file_frame)
        output_frame.pack(fill=X, pady=5)

        ttk.Label(output_frame, text="输出文件:").pack(side=LEFT, padx=5)

        self.output_file_var = tk.StringVar(value="未选择保存路径")
        ttk.Label(output_frame, textvariable=self.output_file_var).pack(side=LEFT, padx=5, fill=X, expand=YES)

        ttk.Button(
            output_frame,
            text="选择路径",
            command=self.select_output_file,
            bootstyle=PRIMARY
        ).pack(side=LEFT, padx=5)

        # 操作按钮区域
        action_frame = ttk.LabelFrame(main_container, text="操作", padding=10)
        action_frame.pack(fill=X, pady=5)

        self.process_btn = ttk.Button(
            action_frame,
            text="处理",
            command=self.process_files,
            bootstyle=SUCCESS
        )
        self.process_btn.pack(side=LEFT, padx=10, pady=5)

        self.save_btn = ttk.Button(
            action_frame,
            text="保存结果",
            command=self.save_result,
            bootstyle=PRIMARY,
            state=DISABLED
        )
        self.save_btn.pack(side=LEFT, padx=10, pady=5)

        # 进度条
        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(
            action_frame,
            variable=self.progress_var,
            maximum=100
        )
        self.progress_bar.pack(side=LEFT, padx=10, pady=5, fill=X, expand=YES)

        # 日志区域
        log_frame = ttk.LabelFrame(main_container, text="日志", padding=10)
        log_frame.pack(fill=BOTH, expand=YES, pady=5)

        self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, state=DISABLED)
        self.log_text.pack(fill=BOTH, expand=YES)

        # 配置日志文本标签样式
        self.log_text.tag_configure("info", foreground="white")
        self.log_text.tag_configure("success", foreground="green")
        self.log_text.tag_configure("warning", foreground="#ffaa00")
        self.log_text.tag_configure("error", foreground="red")
        self.log_text.tag_configure("time", foreground="#66ccff")

        # 初始UI状态更新
        self.update_ui_based_on_function()

    def update_ui_based_on_function(self):
        """根据选择的功能更新UI状态"""
        function = self.function_var.get()

        # 基地址设置只在BIN转HEX和合并BIN时需要
        if function in ["bin2hex", "merge_bin"]:
            self.base_addr_frame.pack(fill=X, pady=5)
        else:
            self.base_addr_frame.pack_forget()

        # 重置文件选择和处理结果
        self.clear_input_files()
        self.output_file = ""
        self.output_file_var.set("未选择保存路径")
        self.processed_data = None
        self.save_btn.config(state=DISABLED)

        # 重置进度条
        self.progress_var.set(0)

        # 日志提示
        function_names = {
            "hex2bin": "HEX转BIN",
            "bin2hex": "BIN转HEX",
            "merge_hex": "合并HEX文件",
            "merge_bin": "合并BIN文件"
        }
        self.log(f"已选择功能: {function_names[function]}")

    def log(self, message, level="info"):
        """添加日志信息"""
        timestamp = datetime.now().strftime("%H:%M:%S")
        self.log_text.config(state=NORMAL)
        self.log_text.insert(tk.END, f"[{timestamp}] ", "time")
        self.log_text.insert(tk.END, f"{message}\n", level)
        self.log_text.config(state=DISABLED)
        self.log_text.see(tk.END)  # 滚动到最新日志

    def add_input_files(self):
        """添加输入文件"""
        function = self.function_var.get()

        # 根据功能选择文件类型
        if function in ["hex2bin", "merge_hex"]:
            file_types = [("HEX文件", "*.hex"), ("所有文件", "*.*")]
            title = "选择HEX文件"
        elif function in ["bin2hex"]:
            file_types = [("BIN文件", "*.bin"), ("所有文件", "*.*")]
            title = "选择BIN文件"
        else:  # merge_bin
            file_types = [("BIN文件", "*.bin"), ("所有文件", "*.*")]
            title = "选择BIN文件"

        # 对于转换功能，只允许选择一个文件
        multiple = function in ["merge_hex", "merge_bin"]

        files = filedialog.askopenfilenames(
            title=title,
            filetypes=file_types,
            multiple=multiple
        )

        if files:
            # 去重并添加新文件
            for file in files:
                if file not in self.input_files:
                    self.input_files.append(file)

            # 更新显示
            if len(self.input_files) <= 3:
                display_text = "; ".join([os.path.basename(f) for f in self.input_files])
            else:
                display_text = f"{len(self.input_files)}个文件: {os.path.basename(self.input_files[0])} 等"

            self.input_files_var.set(display_text)
            self.log(f"已添加 {len(files)} 个文件")

    def clear_input_files(self):
        """清除已选择的输入文件"""
        self.input_files = []
        self.input_files_var.set("未选择文件")
        self.log("已清除所有输入文件")

    def select_output_file(self):
        """选择输出文件路径"""
        function = self.function_var.get()

        # 根据功能选择默认文件扩展名
        if function in ["hex2bin", "merge_bin"]:
            default_ext = ".bin"
            file_types = [("BIN文件", f"*{default_ext}"), ("所有文件", "*.*")]
        else:  # bin2hex, merge_hex
            default_ext = ".hex"
            file_types = [("HEX文件", f"*{default_ext}"), ("所有文件", "*.*")]

        # 生成默认文件名
        default_filename = "output" + default_ext

        file_path = filedialog.asksaveasfilename(
            title="保存输出文件",
            defaultextension=default_ext,
            filetypes=file_types,
            initialfile=default_filename
        )

        if file_path:
            self.output_file = file_path
            self.output_file_var.set(os.path.basename(file_path))
            self.log(f"输出文件将保存为: {os.path.basename(file_path)}")

    def process_files(self):
        """处理文件（转换或合并）"""
        # 检查输入文件
        if not self.input_files:
            messagebox.showwarning("警告", "请先添加输入文件")
            return

        # 对于转换功能，确保只选择了一个文件
        function = self.function_var.get()
        if function in ["hex2bin", "bin2hex"] and len(self.input_files) != 1:
            messagebox.showwarning("警告", f"{'HEX转BIN' if function == 'hex2bin' else 'BIN转HEX'}功能只能选择一个文件")
            return

        # 对于合并功能，确保选择了至少两个文件
        if function in ["merge_hex", "merge_bin"] and len(self.input_files) < 2:
            messagebox.showwarning("警告",
                                   f"{'合并HEX' if function == 'merge_hex' else '合并BIN'}功能需要至少选择两个文件")
            return

        # 开始处理
        self.log("开始处理文件...")
        self.process_btn.config(state=DISABLED)
        self.save_btn.config(state=DISABLED)
        self.progress_var.set(0)

        # 在后台线程中处理，避免UI冻结
        threading.Thread(target=self.perform_processing, daemon=True).start()

    def perform_processing(self):
        """执行实际的文件处理工作"""
        function = self.function_var.get()
        success = False

        try:
            if function == "hex2bin":
                success = self.hex_to_bin()
            elif function == "bin2hex":
                success = self.bin_to_hex()
            elif function == "merge_hex":
                success = self.merge_hex_files()
            elif function == "merge_bin":
                success = self.merge_bin_files()

            if success:
                self.log("文件处理成功", "success")
                self.progress_var.set(100)
                self.after(0, lambda: self.save_btn.config(state=NORMAL))

                # 如果已选择输出路径，自动保存
                if self.output_file:
                    self.after(0, self.save_result)
            else:
                self.log("文件处理失败", "error")
        except Exception as e:
            self.log(f"处理过程出错: {str(e)}", "error")
        finally:
            self.after(0, lambda: self.process_btn.config(state=NORMAL))

    def hex_to_bin(self):
        """将HEX文件转换为BIN文件"""
        hex_path = self.input_files[0]

        try:
            # 加载HEX文件
            self.log(f"加载HEX文件: {os.path.basename(hex_path)}")
            self.after(0, lambda: self.progress_var.set(20))

            ih = intelhex.IntelHex()
            ih.loadhex(hex_path)

            # 获取地址范围
            min_addr = ih.minaddr()
            max_addr = ih.maxaddr()
            self.log(f"HEX文件地址范围: 0x{min_addr:08X} - 0x{max_addr:08X}")
            self.after(0, lambda: self.progress_var.set(40))

            # 获取二进制数据
            self.log("正在转换为BIN格式...")
            self.processed_data = ih.tobinarray(start=min_addr, end=max_addr)
            self.after(0, lambda: self.progress_var.set(80))

            self.log(f"转换成功，BIN数据大小: {len(self.processed_data)} 字节")
            return True
        except Exception as e:
            self.log(f"HEX转BIN失败: {str(e)}", "error")
            return False

    def bin_to_hex(self):
        """将BIN文件转换为HEX文件"""
        bin_path = self.input_files[0]

        try:
            # 解析基地址
            try:
                base_addr = int(self.base_address.get(), 16)
                self.log(f"使用基地址: 0x{base_addr:08X}")
            except ValueError:
                self.log("无效的基地址格式，使用默认基地址0x00000000", "warning")
                base_addr = 0

            # 加载BIN文件
            self.log(f"加载BIN文件: {os.path.basename(bin_path)}")
            self.after(0, lambda: self.progress_var.set(20))

            with open(bin_path, 'rb') as f:
                self.processed_data = f.read()

            self.log(f"BIN文件大小: {len(self.processed_data)} 字节")
            self.after(0, lambda: self.progress_var.set(40))

            # 准备HEX数据
            self.log("正在转换为HEX格式...")
            ih = intelhex.IntelHex()
            ih.puts(base_addr, self.processed_data)

            # 存储HEX对象以便保存
            self.processed_data = ih
            self.after(0, lambda: self.progress_var.set(80))

            self.log(f"转换成功，基地址: 0x{base_addr:08X}")
            return True
        except Exception as e:
            self.log(f"BIN转HEX失败: {str(e)}", "error")
            return False

    def merge_hex_files(self):
        """合并多个HEX文件"""
        try:
            # 创建一个空的IntelHex对象作为合并目标
            merged_ih = intelhex.IntelHex()
            total_files = len(self.input_files)

            for i, hex_path in enumerate(self.input_files):
                # 加载HEX文件
                self.log(f"加载HEX文件 ({i + 1}/{total_files}): {os.path.basename(hex_path)}")
                self.after(0, lambda p=(i + 1) / total_files * 70: self.progress_var.set(p))

                ih = intelhex.IntelHex()
                ih.loadhex(hex_path)

                # 检查地址重叠
                overlap = False
                for addr in ih.addresses():
                    if merged_ih[addr] is not None and merged_ih[addr] != ih[addr]:
                        overlap = True
                        self.log(f"警告: 文件 {os.path.basename(hex_path)} 与之前的文件在地址0x{addr:08X}存在重叠",
                                 "warning")
                        break

                if overlap:
                    # 询问用户是否继续
                    user_continue = [False]

                    def ask_continue():
                        user_continue[0] = messagebox.askyesno(
                            "地址重叠",
                            "检测到地址重叠，是否继续合并（后加载的文件将覆盖先前的内容）？"
                        )

                    self.after(0, ask_continue)
                    # 等待用户响应
                    while user_continue[0] is False:
                        time.sleep(0.1)
                        if user_continue[0] is None:  # 用户取消
                            return False

                # 合并到目标对象
                merged_ih.merge(ih, coverage='replace')

            # 存储合并后的HEX对象
            self.processed_data = merged_ih
            self.after(0, lambda: self.progress_var.set(90))

            # 输出合并结果信息
            min_addr = merged_ih.minaddr()
            max_addr = merged_ih.maxaddr()
            self.log(f"HEX文件合并成功，共合并 {total_files} 个文件")
            self.log(f"合并后地址范围: 0x{min_addr:08X} - 0x{max_addr:08X}")

            return True
        except Exception as e:
            self.log(f"合并HEX文件失败: {str(e)}", "error")
            return False

    def merge_bin_files(self):
        """合并多个BIN文件"""
        try:
            # 解析基地址
            try:
                base_addr = int(self.base_address.get(), 16)
                self.log(f"使用基地址: 0x{base_addr:08X}")
            except ValueError:
                self.log("无效的基地址格式，使用默认基地址0x00000000", "warning")
                base_addr = 0

            # 计算总大小和地址分布
            total_size = 0
            bin_files = []  # 存储(文件路径, 起始地址, 大小)

            for bin_path in self.input_files:
                file_size = os.path.getsize(bin_path)
                bin_files.append((bin_path, base_addr + total_size, file_size))
                total_size += file_size

            # 创建一个空的IntelHex对象作为合并目标
            merged_ih = intelhex.IntelHex()
            total_files = len(bin_files)

            for i, (bin_path, start_addr, file_size) in enumerate(bin_files):
                # 加载BIN文件
                self.log(f"加载BIN文件 ({i + 1}/{total_files}): {os.path.basename(bin_path)}")
                self.log(f"  地址范围: 0x{start_addr:08X} - 0x{start_addr + file_size - 1:08X}")
                self.after(0, lambda p=(i + 1) / total_files * 70: self.progress_var.set(p))

                with open(bin_path, 'rb') as f:
                    bin_data = f.read()

                # 写入数据到指定地址
                merged_ih.puts(start_addr, bin_data)

            # 转换为二进制数据
            min_addr = base_addr
            max_addr = base_addr + total_size - 1
            self.processed_data = merged_ih.tobinarray(start=min_addr, end=max_addr)
            self.after(0, lambda: self.progress_var.set(90))

            self.log(f"BIN文件合并成功，共合并 {total_files} 个文件")
            self.log(f"合并后总大小: {total_size} 字节")
            self.log(f"地址范围: 0x{min_addr:08X} - 0x{max_addr:08X}")

            return True
        except Exception as e:
            self.log(f"合并BIN文件失败: {str(e)}", "error")
            return False

    def save_result(self):
        """保存处理结果"""
        if self.processed_data is None:
            self.log("没有可保存的处理结果", "warning")
            return

        # 如果没有选择输出路径，提示用户选择
        if not self.output_file:
            function = self.function_var.get()
            default_ext = ".bin" if function in ["hex2bin", "merge_bin"] else ".hex"
            default_filename = "output" + default_ext

            file_path = filedialog.asksaveasfilename(
                title="保存输出文件",
                defaultextension=default_ext,
                filetypes=[(f"{'BIN' if default_ext == '.bin' else 'HEX'}文件", f"*{default_ext}"),
                           ("所有文件", "*.*")],
                initialfile=default_filename
            )

            if not file_path:
                return
            self.output_file = file_path
            self.output_file_var.set(os.path.basename(file_path))

        try:
            # 根据数据类型保存
            if isinstance(self.processed_data, intelhex.IntelHex):
                # 保存为HEX文件
                self.processed_data.write_hex_file(self.output_file)
            else:
                # 保存为BIN文件
                with open(self.output_file, 'wb') as f:
                    f.write(self.processed_data)

            self.log(f"文件已成功保存至: {self.output_file}", "success")
            messagebox.showinfo("成功", f"文件已成功保存至:\n{self.output_file}")
        except Exception as e:
            self.log(f"保存文件失败: {str(e)}", "error")
            messagebox.showerror("错误", f"保存文件失败:\n{str(e)}")

    def create_widgets(self):
        pass
